[Previous] [Next]

An Introduction to Dynamic HTML

Entire books have been devoted to Dynamic HTML (DHTML), and I strongly advise you to get one of them if you're seriously interested in producing DHTML programs. As I've done with plain HTML, in this chapter I can only outline the most important features of this language.

In theory, Dynamic HTML should be considered as HTML 4.0, that is, the next version of HTML. In practice, Microsoft Internet Explorer and Netscape Navigator currently support different versions of DHTML, so it's difficult to write DHTML pages that work equally well with both browsers. From our particular point of view as Visual Basic programmers, however, this issue isn't really relevant because DHTML applications actually require Internet Explorer 4.01 Service Pack 1 or later, and they can't run inside another browser. The problem isn't the DHTML language in itself, but the fact that only the most recent versions of the Microsoft browser expose the DHTML events to the outside, whereas a DLL written in Visual Basic 6 can trap them and react accordingly.

Main Features

Dynamic HTML isn't radically different from regular HTML. All the old tags are still supported, and scripts inside the page can exploit an expanded object model that's compatible with the previous version, so they'll continue to work as before. In a sense and at the risk of oversimplifying, we can say that the real difference between regular HTML and Dynamic HTML is in how the page is interpreted by the browser when the page is being downloaded from the remote server.

Among the new features of DHTML, the following ones deserve special mention:

Let's see the most important new features in more detail.

Tags

You've already seen how you can use the <DIV> and </DIV> tags to group multiple elements and create a portion of the page that can be assigned a common style. For example, you can use these tags to create rectangular areas with text and background colors that are different from the other elements:

<DIV STYLE=" WIDTH=300; HEIGHT=100; COLOR=white; BACKGROUND=red;">
A red block with white text<BR>
Another line in the same block
</DIV>

When you're working with DHTML, you might need to process items that are smaller than the heading or the paragraph. You can reference such items using the <SPAN> and </SPAN> pair of tags, which subdivide an element into smaller chunks so that each portion can have different attributes:

<DIV STYLE="WIDTH=300; HEIGHT=150; COLOR=white; BACKGROUND=red;">
A red block with white text<BR>
<SPAN STYLE="COLOR=yellow">Some words in yellow,</SPAN>
<SPAN STYLE="COLOR=blue">Other words in blue</SPAN>
</DIV>

An important difference between the <DIV> tag and the <SPAN> tag is that the former always adds a carriage return after the closing </DIV> tag, which means that you can't continue to insert text on the same line. Conversely, the </SPAN> tag doesn't insert a carriage return, so, for example, the previous code snippet produces two lines of text, not three. The importance of the <DIV> and <SPAN> tags will be more evident when you see how you can use scripts to create dynamic pages.

The <BUTTON> and </BUTTON> tags allow you to add more versatile button controls on a form. While the standard <INPUT TYPE=Button> tag supports only a text caption, these new tags let you embed anything in the text, including an image:

<BUTTON ID="Button1" STYLE="height=80; width=180">
Click Here
<IMG src="www.vb2themax.com/mylogo.gif">
</BUTTON>

DHTML includes a sort of Frame control, which can draw a border around other controls. You create such a control using the <FIELDSET> tag and specify its caption using the <LEGEND> tag. Actually, this frame control is even more powerful than its Visual Basic counterpart because you can embed nearly everything between the <LEGEND> and </LEGEND> tag pair:

<FIELDSET>
<LEGEND>Select a product<IMG src="mylogo.gif"></legend>
<INPUT TYPE=Radio NAME="Product" CHECKED>Tape
<INPUT TYPE=Radio NAME="Product">Music CD
<INPUT TYPE=Radio NAME="Product">Videotape
</FIELDSET>

Dynamic HTML also adds several new attributes that you can use with certain tags. For example, the TABINDEX attribute lets you specify the tab order of controls on the page, exactly as the Visual Basic property does. The ACCESSKEY attribute works with some types of page elements to provide a hot key for easy Alt+key selection. The difference is that DHTML doesn't highlight the selected key in any way—you have to do it yourself. While this failure to highlight a selected key seems a flaw in DHTML, it actually gives you a lot of flexibility when building your user interface:

' A "Click Here" button that you click using the Alt+H key combination
<BUTTON ID="Button1" ACCESSKEY="H">Click <B>H</B>ere</BUTTON>

Finally, the DISABLED attribute lets you selectively disable (and reenable) controls and other elements. You just need to remember that it works in a way opposite to the Visual Basics Enabled property:

<INPUT TYPE=Radio ID="optMusicCD" NAME="Product" DISABLED>Music CD

<SCRIPT LANGUAGE="VBScript">
Sub Button1_onclick()
    ' Reenable the option button.
    optMusicCD.disabled = False
End sub
</SCRIPT>

Properties

Dynamic HTML adds some new properties to the <STYLE> tag. These properties are useful in themselves, but above all they add a new dimension to scripting because they allow a script routine to move, hide, and change the relative z-order of the elements on the page, so making the page a truly dynamic one.

The position property permits you to accurately place an element on the page; by default this property is set to the value static, which means that the element is positioned according to the usual rules of HTML. But if you set the position property to absolute, you can specify the coordinates of an object with respect to the upper left corner of the window, using the left and top properties. Here's an example that displays white text inside a rectangle with a red background. The rectangle is 300 pixels wide and 150 pixels high:

<DIV STYLE="POSITION=absolute; TOP=50; LEFT=100; WIDTH=300; HEIGHT=150; 
COLOR=white; BACKGROUND=red;">A red block with white text</DIV>

If the object is contained within another object—for example, another <DIV> section—the left and top coordinates are measured with respect to the container's upper left corner. For example, the following piece of code creates a red rectangle and a blue rectangle within it:

<DIV STYLE="POSITION=absolute; TOP=100; LEFT=100; WIDTH=300; HEIGHT=150; 
COLOR=white; BACKGROUND=red;">
Outer rectangle
   <DIV STYLE="POSITION=absolute; TOP=20; LEFT=40; WIDTH=220; HEIGHT=110; 
   COLOR=white; BACKGROUND=Blue;">Inner rectangle</DIV>
</DIV>

If position is set to relative, the left and top properties refer to the upper left corner of the element in the page that immediately precedes the current one. You typically use relative mode to move a portion of text or an image to a given distance from the last piece of text in the page:

A string of text followed by a green rectangle
<DIV STYLE="POSITION=relative; TOP:10; LEFT=0; WIDTH=300; HEIGHT=10; 
BACKGROUND=green;"></DIV>

When you have overlapping objects on the page, you can determine their visibility using the z-order property, by means of which a higher value puts an object in front of objects with lower values:

<DIV STYLE="POSITION=absolute; TOP=100; LEFT=100; WIDTH=300; 
HEIGHT=150; COLOR=white; BACKGROUND=red; Z-INDEX=2">
This rectangle overlaps the next one.</DIV>
<DIV STYLE="POSITION=absolute; TOP=120; LEFT=120; WIDTH=300; 
HEIGHT=150; COLOR=white; BACKGROUND=green; Z-INDEX=1"></DIV>

You can't use the z-order property to change the relative z-ordering of an object and its container because the container will always appear behind the objects it contains. If you omit the z-order property, objects stack according to the order they appear in the HTML source code. (That is, each object covers the object defined before it in code.)

The visibility property specifies whether the object is visible. It takes the values hidden or visible. This property is most useful when it's controlled via script. Another new intriguing property is display: When you set it to none, the element becomes invisible and the browser reclaims the space this element occupied to rearrange the other elements on the page (unless they use absolute positioning). You can make the element visible again by setting the display property back to an empty string. For an example of this property, see the "The First Example: A Dynamic Menu" section later in this chapter.

Properties and Scripting

The "dynamic" in Dynamic HTML means that you can modify one or more attributes of the page at run time and have the browser immediately render the new contents of the page without needing to reload the page from the server. For this reason, you must create script procedures to exploit the potential of DHTML.

You can programmatically control any attribute of any item on the page, provided that the item can be referenced in code. In plain HTML, you can reference only a few items—for example, the controls in a form—but in Dynamic HTML, you can reference any item that has an ID attribute. For example, the following code contains a <DIV> portion of the page associated with the rectangle ID, and a push button that, when clicked, executes a VBScript routine that modifies the background color of the <DIV> section:

<DIV ID="rectangle" STYLE="POSITION=absolute; LEFT=100; 
TOP=50; WIDTH=200; HEIGHT=100; BACKGROUND=red">
Click the button to change background color
</DIV>
<FORM>
<INPUT TYPE=BUTTON NAME="ChangeColor" VALUE="Change Color">
</FORM>

<SCRIPT LANGUAGE="VBScript">
' Randomly change the color of the rectangle.
Sub ChangeColor_onclick()
    Rectangle.style.background = "#" & RndColor() & RndColor() & RndColor()
End Sub

' Return a random two-digit hexadecimal value.
Function RndColor()
    RndColor = Right("0" & Hex(Rnd * 256), 2)
End Function
</SCRIPT>

You need to pass through the intermediate style object to get to the background property. This makes sense because background is a property of the STYLE attribute. Likewise, you can control other properties of the style object, such as these:

To fine-tune the position and size of an item, you can strip the px characters appended to the value returned by the left, top, width and height properties. Using the posxxxx properties is usually better, however, because they return numerical values. The following example shows how you can move an element to the right:

rectangle.style.posLeft = rectangle.style.posLeft + 10

If a property isn't defined in the STYLE attribute, it returns Null. The posxxxx properties are an exception to this rule because they always return numeric values.

NOTE
Use the style.color and style.backgroundColor properties to adjust the text and background color of any element on the page except the Document object, for which you should use the fgcolor and bgcolor properties.

Text Properties and Methods

Because a DHTML document is an active entity, you often want to modify its contents at run time. You can make run-time modifications in many ways, for example, by using the TextRange object (which is described later in this chapter). Most visible page elements, however, support four properties and two methods that make this an easy job.

The four properties are innerText, outerText, innerHTML, and outerHTML. The innerText returns the portion of the document contained in the element as text. (All HTML tags are automatically filtered out.) The outerText property returns the same value as innerText, but you get a different result when you assign a string to it, as you'll see in a moment. The innerHTML property returns the HTML code between the opening and closing tags. The outerHTML property returns the HTML code of the element, including its opening and closing tags.

To experiment with these properties, let's define an element that contains some HTML tags inside it, such as

<H1 ID=Heading1>Level <I>One</I> Heading</H1>

which is rendered on your browser as Level One Heading. Now see what the preceding properties return when applied to this element:

MsgBox Heading1.innerText  ' Level One Heading
MsgBox Heading1.outerText  ' Level One Heading
MsgBox Heading1.innerHTML  ' Level <I>One</I> Heading
MsgBox Heading1.outerHTML  ' <H1 ID=Heading1>Level <I>One</I> Heading</H1>

Assigning a value to the innerText substitutes the text between the opening and closing tags; the new value isn't parsed, so it shouldn't include HTML tags. For example, the statement

Heading1.innerText = "A New Heading"

completely replaces the text between the <H1> and </H1> tags, and the new heading appears in your browser as A New Heading. Even if the outerText property always return the same string as the innerText property, it behaves differently when a new value is assigned to it because the substitution also affects the surrounding tags. Hence the statement

Heading1.outerText = "A New Heading"

actually destroys the <H1> and </H1> tags and transforms the heading element into plain text (unless it was contained in another pair of tags). What's worse is that now the object has no ID attribute associated with it, so you can't programmatically access it any longer. For this reason, the outerText property has a limited practical use, and in most cases you'll use it just to delete the tags that surround an element:

' A reusable VBScript routine
Sub DeleteOuterTags(anyElement)
    anyElement.outerText = anyElement.innerText
End Sub

If you want to replace the portion of a page inside a pair of tags with some HTML text, you should use that element's innerHTML property, as in this line of code:

Heading1.innerHTML = "A <U>New</U> Heading"

In this case, the string passed to the property is parsed and all HTML tags affect the result. For example, after the previous assignment the result displayed in the browser is A New Heading.

The last property of this group, outerHTML, works like innerHTML, but the substitution also affects the surrounding tags. This means that you can modify the type and the ID of the element you're referencing, and you can change, for example, the level of a heading and the formatting of its contents in one operation:

Heading1.outerHTML = "<H2 ID=Heading1>Level <U>Two</U> Heading</H2>"

Or you can center the heading using this code:

 Heading1.outerHTML = "<CENTER>" & Heading1.outerHTML & "</CENTER>"

Thanks to the string manipulation capabilities of VBScript, you can create a reusable routine that lets you change the level of any heading in the page without altering either its ID or its contents:

Sub ChangeHeadingLevel(element, newLevel)
    html = element.outerHTML
    pos1 = Instr(UCase(html), "<H")
    level = Mid(html, pos1 + 2, 1)
    pos2 = InstrRev(UCase(html), "</H" & level, -1, 1)
    ' Because VBScript doesn't support the "_" continuation character,
    ' you must type the next two lines as a single statement.
    html = Left(html, pos1 + 1) & newLevel & Mid(html, pos1 + 3, 
        pos2 - pos1) & newLevel & Mid(html, pos2 + 4)
    element.outerHTML = html
End Sub

If you modify an element's ID, the event procedure you've written for it won't work any longer. For this reason, you should always keep the same ID, or you should dynamically add the code to manage events from the new element. Also keep in mind that not all visible elements support all these four properties, the most notable exception being table cells (which expose only the innerText and innerHTML properties).

While the four properties I've described so far let you replace a portion of the document, most elements also support two methods that enable you to add new contents to the document. The insertAdjacentText method inserts a portion of plain text immediately before or after the opening or the closing tag of the element. The insertAdjacentHTML method does the same, but its argument is parsed and all HTML is correctly recognized and affect the result. Here are a few examples:

' Append plain text at the end of the heading.
Heading1.insertAdjacentText "BeforeEnd", " (added dynamically)"
' As above, but appends italicized text.
Heading1.insertAdjacentHTML "BeforeEnd", " <I>(added dynamically)</I>"
' Add new text before the first word of the heading.
Heading1.insertAdjacentText "AfterBegin", "This is a "
' Add a level 2 heading immediately after this heading.
Heading1.insertAdjacentHTML "AfterEnd", "<H2>New Level 2 Heading</H2>"
' Insert italicized text right before this heading.
Heading1.insertAdjacentHTML "BeforeBegin", "<I>Introducing...</I>"

Events

Each page element with which you've associated an ID attribute can raise an event. The majority of DHTML events are similar to Visual Basic events, even though their names are different. All the DHTML events begin with the two characters on, such as onclick, onkeypress, and onchange. For example, when the hyperlink

Click <A ID="Details" href="www.vb2themax.com/details">here</a> for details

is clicked, you can trap the user's action with the following VBScript code:

Sub Details_onclick()
    MsgBox "About to be transferred to another site"
End Sub

The handling of DHTML events differs significantly in a couple of ways from the Visual Basic' way of managing events, though. First, event procedures don't take arguments. Second, an event is received by the object that raised it (which is like the innermost of a set of Russian dolls) and in turn by all the page elements that contain the object that raised the event. (This feature, known as event bubbling, is explained in the following section.)

All the arguments that make sense inside an event can be retrieved as (and possibly assigned to) properties of the event object. For example, when an onkeypress event is received, you can determine which key has been pressed by looking at the event.keycode property, and you can also "eat" the key by setting this property to 0. For example, see how you can convert to uppercase all the text entered in a TextBox control:

<INPUT TYPE=Text NAME="txtCity" VALUE="">

<SCRIPT LANGUAGE="VBScript">
Sub txtCity_onkeypress()
    window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
End Sub
</SCRIPT>

Inside any event procedure, you can retrieve a reference to the object the event is bound to by using the Me keyword, as in the following piece of code:

Sub txtCity_onkeypress()
    ' Clear the text box if the spacebar is pressed.
    If window.event.keycode = 32 Then 
        Me.Value = ""
        window.event.keycode = 0      ' Also eat the key.
    End If
End Sub

Event bubbling

The event bubbling feature of DHTML events lets you process an event in multiple places on the page, which isn't something you can do in Visual Basic. A DHTML event is first received by the object acted on by the user, it's next raised for its container and then for the container's container, and so on until the event reaches the highest tag in the hierarchy. For example, if the user clicks on a hyperlink inside a table, the onclick event is first fired for the hyperlink object, then for the table, for the Body object, and finally for the Document object.

The following example makes use of the event bubbling feature to write one event procedure that manages the keys pressed in three distinct TextBox controls, which have been grouped together under a <DIV> tag. The example also demonstrates that the event is generated for the Body object (provided that you label it with an ID attribute) and for the Document object:

<BODY ID="Body">
<DIV ID=Textboxes>
<INPUT TYPE=Text NAME="txtName" VALUE="">
<INPUT TYPE=Text NAME="txtCity" VALUE="">
<INPUT TYPE=Text NAME="txtCountry" VALUE="">
</DIV>

<SCRIPT LANGUAGE="VBScript">
Sub Textboxes_onkeypress()
    ' Convert to uppercase.
    window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
End Sub

Sub Body_onkeypress()
    ' The Body element also gets the event.
End Sub

Sub Document_onkeypress()
    ' The Document element also gets the event.
End Sub
</SCRIPT>
</BODY>

By setting the event.cancelBubble property to True, you can cancel the bubbling in any event procedure. For example, if you set this property to True in the Body_onclick procedure, the Document object won't receive the event.

In any event procedure in the event chain, you can retrieve a reference to the element that started the event by querying the event.srcElement property. This permits you to create generalized event procedures and at the same time to account for special cases, as in the following example:

Sub Textboxes_onkeypress()
    ' Convert all textboxes to uppercase except txtName.
    If window.event.srcElement.Name = <> "txtName" Then
        window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
    End If
End Sub 

Don't confuse the srcElement property with the Me keyword, which returns a reference to the object the event procedure is bound to. The two objects coincide only inside the first event procedure fired by the event bubbling mechanism.

Canceling the default effect

Most user actions on a page element produce default results. For example, a mouse click on a hyperlink causes a jump to another HTML page, and a key pressed when the focus is on a TextBox control causes the character to be added to the control's current contents. You can cancel this default action by assigning False to the event.returnValue property, as in this example:

Click <A ID="Link1" href="http://www.vb2themax.com">here</a>

<SCRIPT LANGUAGE="VBScript">
Sub Link1_onclick()
    'Prevent the yperlink from firing.
    window.event.returnValue = False
End Sub
</SCRIPT>

Another way to cancel the default action of an event is to transform the event procedure into a Function and assign False to the return value, as here:

Function Link1_onclick()
    Link1_onclick = False
End Function

Timer events

Even if HTML doesn't provide a Timer control, it's possible—indeed simple—to create routines that are executed at regular intervals. You can choose from two types of timer routines: one that fires repeatedly and one that fires only once. (This in fact is a standard HTML feature, so you don't need DHTML for using the code in this section.) You activate a timer routine using the setTimeout (for one-shot timers) or setInterval (for regular timers) methods of the window object. These methods have a similar syntax:

window.setTimeout "routinename", milliseconds, language
window.setInterval "routinename", milliseconds, language

You normally invoke these methods from within the window_onload routine or outside any routine. (In both cases, the methods are executed as soon as the page is downloaded.) For example, the following code moves a button to the right by 20 pixels twice per second.

<INPUT TYPE=BUTTON NAME="Button1" VALUE="Button Caption" 
    STYLE="POSITION=absolute" >

<SCRIPT LANGUAGE="VBScript">
' This line is executed when the page is loaded.
window.setInterval "TimerEvent", 500, "VBScript"

' The following routine is executed every 500 milliseconds.
Sub TimerEvent()
    Button1.style.posLeft = Button1.style.posLeft+5
End Sub
</SCRIPT>

You can cancel the effect of a setTimeout or a setInterval method by using the clearTimeout or the clearInterval method, respectively.

Event summary

We can subdivide DHTML events into a few categories, according to their functions.

Keyboard events include onkeypress, onkeydown, and onkeyup. These are similar to the Visual Basic events of same names. The event object's keycode property contains the code of the pressed key , and you can read the state of shift keys by means of the altKey, ctrlKey, and shiftKey properties of the event object.

Dynamic HTML supports the same mouse events as Visual Basic, including onclick, ondblclick, onmousedown, onmouseup, and onmousemove. The onclick event also fires when the user presses Enter while a push button has the focus. Inside a mouse event you can query the event.button property to learn which button was pressed. (The bit-coded value you get is similar to the argument received by Visual Basic's mouse events.)

Several DHTML events have no counterparts in Visual Basic: onmouseover fires when the mouse cursor hovers over an element, and onmouseout when the mouse abandons an element. Inside these event procedures you can use the fromElement and toElement properties of the event object to learn which element has been entered or abandoned.

The onfocus and onblur events are similar to Visual Basic's GotFocus and LostFocus events, but they also fire when the focus goes to another window or another application. The onchange event is similar to the corresponding Visual Basic event, but it fires only when the focus leaves the control.

The onselectstart event fires when the user clicks on the page and starts selecting a portion of text or other elements; when the mouse moves and the selected area changes accordingly, an onselect event is fired. The ondragstart event fires when a drag operation starts: By trapping this event, you can cancel its default action, which is copying the selected text elsewhere.

A few events are global to the entire page. The onreadystatechange event fires when the state of the page changes (for example, when the download has completed and the page is about to become interactive). The onresize event fires when the page is resized. The onunload and onbeforeunload events are similar to Visual Basic's Unload and QueryUnload, and they fire when the page is about to be unloaded because the user is navigating to another page or closing the browser. The onscroll event occurs when the document (or a page element) is scrolled. The onhelp event fires when the user presses the F1 key. The onerror event fires when a script error occurs or when the download of a page element (for example, an image) fails.

A few events can't be trapped from a DHTML Visual Basic application: onabort (the user clicks on the Stop button on the browser's toolbar), onreset (the user clicks on the Reset button), and onsubmit (the user clicks on the Submit button).